package abacab.actor;

import abacab.router.Router;

/**
 * Represents an actor definition
 * 
 * @param <M>
 *          the type of handled messages
 */
public abstract class ActorDef<M> {
	/**
	 * Get a base actor's definition
	 * 
	 * @param builder
	 *          the builder
	 * @return the actor's definition
	 */
	public static <M> ActorDef<M> create(ActorBuilder<M> builder) {
		return new BuildActorDef<M>(builder);
	}

	/**
	 * Get a base actor's definition
	 * 
	 * @param actorClass
	 *          the actor's class
	 * @return the actor's definition
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static ActorDef create(Class<? extends Actor> actorClass) {
		return new BuildActorDef(actorClass);
	}

	/**
	 * Put the actor(s) behind a router
	 * 
	 * @param name
	 *          the router's name
	 * @return the new definition
	 */
	public ActorDef<M> withRouter(String name) {
		return new RouterActorDef<M>(this, name);
	}

	/**
	 * Put the actor(s) behind a router
	 * 
	 * @param router
	 *          the router
	 * @return the new definition
	 */
	public ActorDef<M> withRouter(Router<M> router) {
		return new FinalRouterActorDef<M>(this, router);
	}

	/**
	 * Execute the actor(s) on a particular executor
	 * 
	 * @param name
	 *          the executor's name
	 * @return the new definition
	 */
	public ActorDef<M> withExecutor(String name) {
		return new ExecutorActorDef<M>(this, name);
	}

	/**
	 * Use a particular queue implementation for the actor(s)
	 * 
	 * @param name
	 *          the queue's name
	 * @return the new definition
	 */
	public ActorDef<M> withQueue(String name) {
		return new QueueActorDef<M>(this, name);
	}

	/**
	 * @return the builder for the actor
	 */
	public ActorBuilder<M> getBuilder() {
		return null;
	}

	/**
	 * @return the router
	 */
	public Router<M> getRouter() {
		return null;
	}

	/**
	 * @return the router's name
	 */
	public String getRouterName() {
		return null;
	}

	/**
	 * @return the executor's name
	 */
	public String getExecutorName() {
		return null;
	}

	/**
	 * @return the queue's name
	 */
	public String getQueueName() {
		return null;
	}

	/**
	 * The basic definition providing only with a way of building the actor
	 * 
	 * @param <M>
	 *          the type of handled messages
	 */
	private static final class BuildActorDef<M> extends ActorDef<M> {
		/** The builder */
		private final ActorBuilder<M>	builder;

		/**
		 * @param builder
		 *          the builder
		 */
		public BuildActorDef(ActorBuilder<M> builder) {
			this.builder = builder;
		}

		/**
		 * @param actorClass
		 *          the actor's class
		 */
		public BuildActorDef(Class<? extends Actor<M>> actorClass) {
			this.builder = new ReflexionBuilder<M>(actorClass);
		}

		@Override
		public ActorBuilder<M> getBuilder() {
			return this.builder;
		}

		/**
		 * Uses reflexivity to build the actor
		 * 
		 * @param <M>
		 *          the type of handled messages
		 */
		private static final class ReflexionBuilder<M> implements ActorBuilder<M> {
			/** The actor's class */
			private final Class<? extends Actor<M>>	actorClass;

			/**
			 * @param actorClass
			 *          the class of the actor
			 */
			ReflexionBuilder(Class<? extends Actor<M>> actorClass) {
				this.actorClass = actorClass;
			}

			@Override
			public Actor<M> build() {
				try {
					return this.actorClass.newInstance();
				}
				catch (InstantiationException e) {
					throw new IllegalArgumentException("Unable to create an instance of the actor", e);
				}
				catch (IllegalAccessException e) {
					throw new IllegalArgumentException("Unable to create an instance of the actor", e);
				}
			}
		}
	}

	/**
	 * A decoration
	 * 
	 * @param <M>
	 *          the type of handled messages
	 */
	private static abstract class WrappedActorDef<M> extends ActorDef<M> {
		/** The wrapped actor definition */
		private final ActorDef<M>	wrappedDef;

		/**
		 * Create the definition
		 * 
		 * @param wrappedDef
		 *          the wrapped actor definition
		 */
		protected WrappedActorDef(ActorDef<M> wrappedDef) {
			this.wrappedDef = wrappedDef;
		}

		@Override
		public ActorBuilder<M> getBuilder() {
			return this.wrappedDef.getBuilder();
		}

		@Override
		public Router<M> getRouter() {
			return this.wrappedDef.getRouter();
		}

		@Override
		public String getRouterName() {
			return this.wrappedDef.getRouterName();
		}

		@Override
		public String getExecutorName() {
			return this.wrappedDef.getExecutorName();
		}

		@Override
		public String getQueueName() {
			return this.wrappedDef.getQueueName();
		}
	}

	/**
	 * Decorates with a router
	 * 
	 * @param <M>
	 *          the type of handled messages
	 */
	private static class RouterActorDef<M> extends WrappedActorDef<M> {
		/** The router's name */
		private final String	routerName;

		/**
		 * Create the definition
		 * 
		 * @param wrappedDef
		 *          the wrapped actor definition
		 * @param routerName
		 *          the router's name
		 */
		protected RouterActorDef(ActorDef<M> wrappedDef, String routerName) {
			super(wrappedDef);
			this.routerName = routerName;
		}

		@Override
		public String getRouterName() {
			return this.routerName;
		}
	}

	/**
	 * Decorates with a router
	 * 
	 * @param <M>
	 *          the type of handled messages
	 */
	private static class FinalRouterActorDef<M> extends WrappedActorDef<M> {
		/** The router */
		private final Router<M>	router;

		/**
		 * Create the definition
		 * 
		 * @param wrappedDef
		 *          the wrapped actor definition
		 * @param router
		 *          the router
		 */
		protected FinalRouterActorDef(ActorDef<M> wrappedDef, Router<M> router) {
			super(wrappedDef);
			this.router = router;
		}

		@Override
		public Router<M> getRouter() {
			return this.router;
		}
	}

	/**
	 * Decorates with an executor
	 * 
	 * @param <M>
	 *          the type of handled messages
	 */
	private static class ExecutorActorDef<M> extends WrappedActorDef<M> {
		/** The executor's name */
		private final String	executorName;

		/**
		 * Create the definition
		 * 
		 * @param wrappedDef
		 *          the wrapped actor definition
		 * @param executorName
		 *          the executor's name
		 */
		protected ExecutorActorDef(ActorDef<M> wrappedDef, String executorName) {
			super(wrappedDef);
			this.executorName = executorName;
		}

		@Override
		public String getExecutorName() {
			return this.executorName;
		}
	}

	/**
	 * Decorates with a queue
	 * 
	 * @param <M>
	 *          the type of handled messages
	 */
	private static class QueueActorDef<M> extends WrappedActorDef<M> {
		/** The queue's name */
		private final String	queueName;

		/**
		 * Create the definition
		 * 
		 * @param wrappedDef
		 *          the wrapped actor definition
		 * @param queueName
		 *          the queue's name
		 */
		protected QueueActorDef(ActorDef<M> wrappedDef, String queueName) {
			super(wrappedDef);
			this.queueName = queueName;
		}

		@Override
		public String getQueueName() {
			return this.queueName;
		}
	}
}